
Introduction
In this article, I will explain how to create a tab strip control within ASP.NET 2.0 using existing controls. We all had hoped that with the release of .NET 2.0, Microsoft would incorporate many WebForm controls required for every day use, such as a TabStrip, LoginXXX, etc. They provided us with more controls than we know what to do with, however, there is still no tab strip control within ASP.NET 2.0. But there is good news. With three of the new 2.0 controls (System.Web.UI.WebControls.MultiView
, System.Web.UI.WebControls.View
, and System.Web.UI.WebControls.Menu
), a tab strip is actually quite easy to build.
Instead of building a WebForms page with these controls incorporated into the page, I will build a System.Web.UI.WebControls.CompositeControl
, which is also new within the 2.0 framework.
CompositeControl
Microsoft MSDN: "A CompositeControl
is an abstract class that provides naming container and control designer functionality for custom controls that encompass child controls in their entirety, or use the functionality of other controls... The CompositeControl
class implements the INamingContainer
interface. This is required to ensure that all child control ID attributes are unique, and can be located on post back for data binding."
I think you can tell where I am headed… I am going to build a composite control that contains an embedded MultiView
, View
, and Menu
to mimic the look and feel of a tabstrip. All I need to do is add the MultiView
, View
, and Menu
controls into the CompositeControl
's control hierarchy (in the correct order, of course), and let each of the controls render themselves. The advantage of using existing controls is that they know how to render themselves, therefore I do not have to write any rendering logic.
I not going to examine the MultiView
, View
, and Menu
controls in detail, however. You will need to have a thorough understanding of these controls before proceeding. Therefore, the links below will take you to MSDN, if needed:
Building the CompositeControl – TabularMultiView
Now that you have a thorough understanding of the basic functionality of each control, I will use them as follows to mimic a tabstrip. The Menu
control will be used to display the actual tabs, such that, each MenuItem
will represent a tab in a horizontal fashion. The MultiView
will contain the View
s, in which each View
will contain the content for a particular tab. There will be a 1 to 1 correspondence to each MenuItem
and View
. Meaning tab 0, or MenuItem
0 will be associated with View
0, tab 1 with View
1, etc.
The base functionality provided by the MultiView
, View
, and Menu
controls is sufficient to build a tab strip. To make it more developer friendly, I needed to extend the View
control to incorporate additional properties and events. Therefore, I created a TabularView
class, which extends the View
control. The following properties were added:
TabName
(String
) – MenuItem
’s Text
property.
Selectable
(Boolean
) – Whether or not the tab is selectable.
Tooltip
(String
) – Tooltip for the MenuItem
.
Public Class TabularView
Inherits View
Public Property TabName as String
...
End Property
Public Property Selectable as Boolean
...
End Property
Public Property ToolTip as String
...
End Property
...
End Class
Now to the Real Code
The composite control will be named TabularMultiView
, which inherits CompositeControl
.
Public Class TabularMultiView
Inherits CompositeControl
End Class
Without going into too much detail about the CompositeControl
class, one of the most important methods is the CreateChildControls()
method. This method is used to build the control’s control hierarchy. Thus, this is where the embedded Menu
and MultiView
controls are added to the control hierarchy, such as:
Protected Sub CreateChildControls()
…
Dim menu as
new Menu
Dim mltView as new MultiView
Me.Controls.Add(menu)
Me.Controls.Add(mltView)
…
End Sub
Note: The code above does not produce anything meaningful.
Now I needed a way to embed each tab's content within this new TabularMultiView
control. More specifically, how was I going to take advantage of my TabularView
control, which extends System.Web.UI.WebControls.View
? This is done by creating a strongly-typed collection of TabularView
s and adding a property named 'ParseChildren
' to the TabularMultiView
control to allow inner-property controls of type TabularView
.
This is accomplished with the following:
DefaultProperty("Views"), _
ParseChildren(True, "Views")> _
Public Class TabularMultiView
Inherits CompositeControl
private _List as List(Of TabularView)
<Category("Behavior"), _
Description("The TabularView collection"), _
DesignerSerializationVisibility _
(DesignerSerializationVisibility.Content), _
PersistenceMode(PersistenceMode.InnerDefaultProperty)> _
Public ReadOnly Property Views() As List(Of TabularView)
Get
If _List Is Nothing Then
_List = New List(Of TabularView)
End If
Return _List
End Get
End Property
…
End Class
As you can see, the TabularMultView
's Views
property is assigned a strongly-typed collection of TabularView
s. The design-time code would look something like the following:
<cc:TabularMultiView ID="tabMltView" runat="server">
<cc:TabularView ID="TabularView1" runat="server"
TabName="TabName1">
Content Here…
</cc:TabularView>
<cc:TabularView ID="TabularView2" runat="server"
TabName="TabName2">
Content Here…
</cc:TabularView>
</cc:TabularMultiView>
The TabularMultView
's Views
property will be set prior to the control's Load
event. It will be done automatically, thanks to the 'ParseChildren
' class property. Since the TabularView
extends the View
, there isn’t much that you need to do to add the TabularView
s to the Multiview
's Views
collection, other than iterating through the TabularMultiView
's Views
property to extract each TabularView
and adding each TabularView
to the internal Multiview
’s Views
collection. Such as:
For Each tabView As TabularView In Views
InnerMultiView.Views.Add(tabView)
Next
To assign the ActiveIndex
of the MultView
, add an ActiveIndex
property to the TabularMultiView
control. Once you have built the MultiView
and added each TabularView
to its Views
collection, you will need to set the ActiveIndex
value. I will do it within the CreateChildControls
method, for simplicity.
If InnerMultiView.Views.Count >= 0 AndAlso Me.ActiveIndex _
< InnerMultiView.Views.Count Then
InnerMultiView.ActiveViewIndex = Me.ActiveIndex
End If
Incorporating the Menus - Tabs
As you are iterating through the TabularMultView
's Views
collection and adding each TabularView
to the MultiView
's Views
collection, we need to add a new MenuItem
to the Menu
control as well.
For Each tabView As TabularView In Views
InnerMultiView.Views.Add(tabView)
InnerMenu.Items.Add(new MenuItem("name", "value"))
Next
Remember, we added the TabName
property to the TabularView
? This TabName
property of the TabularView
will become the MenuItem
’s Text
value. The same can be applied for the Selectable
property and so forth. Also, we need to assign each MenuItem
’s Value
property, so when the MenuItem
is clicked, we can associate the MenuItem
/Tab
with the appropriate TabularView
. More specifically, when a MenuItem
is clicked, the value of the MenuItem
will dictate the ActiveIndex
property of the MultiView
. Now, we have the following:
Dim index as Integer = 0
Dim menuItem as MenuItem
For Each tabView As TabularView In Views
InnerMultiView.Views.Add(tabView)
menuItem = new MenuItem(tabView.TabName, index)
menuItem.Selectable = tabView.Selectable
InnerMenu.Items.Add(new menuItem)
index += 1
Next
After initializing the Menu
, and prior to rendering, add a menu OnClick
handler (ideally in the CreateChildControls
method):
InnerMenu = New Menu()
AddHandler m_Menu.MenuItemClick, AddressOf Menu_MenuItemClick
Within the menu's Click
handler, extract the selected MenuItem
's value which will be the index we assigned previously (see above). Then, assign the ActiveIndex
of the MultiView
to the MenuItem
's value.
Protected Sub Menu_MenuItemClick(ByVal sender As System.Object, _
ByVal e As System.Web.UI.WebControls.MenuEventArgs)_
InnerMultiView.ActiveViewIndex = CInt(e.Item.Value)
RaiseEvent MenuItemClick(sender, e)
End Sub
Putting it all Together - CreateChildControls() Method
Protected Overrides Sub CreateChildControls()
InnerMenu = New Menu()
AddHandler m_Menu.MenuItemClick, AddressOf Menu_MenuItemClick
InnerMenu.ID = "tigerMenu"
InnerMenu.Orientation = Orientation.Horizontal
InnerMenu.CssClass = Me.TabularMenuCSS
If String.IsNullOrEmpty(Me.TabularMenuItemCSS) Then
InnerMenu.StaticMenuItemStyle.BackColor = Color.DarkGray
InnerMenu.StaticMenuItemStyle.ForeColor = Color.White
Else
InnerMenu.StaticMenuItemStyle.CssClass = Me.TabularMenuItemCSS
InnerMenu.StaticMenuItemStyle.HorizontalPadding = 0
InnerMenu.StaticMenuItemStyle.VerticalPadding = 0
End If
If String.IsNullOrEmpty(Me.TabularMenuItemCSS) Then
InnerMenu.StaticSelectedStyle.BackColor = Color.LightGray
InnerMenu.StaticSelectedStyle.ForeColor = Color.White
Else
InnerMenu.StaticSelectedStyle.CssClass = _
Me.TabularMenuSelectedItemCSS
InnerMenu.StaticSelectedStyle.HorizontalPadding = 0
InnerMenu.StaticSelectedStyle.VerticalPadding = 0
End If
Dim index As Integer = 0
Dim mItem As MenuItem
For Each tabView As TabularView In Views
mItem = New MenuItem(IIf(String.IsNullOrEmpty(tabView.TabName), _
SetTabTextWidth(tabView.ID), _
SetTabTextWidth(tabView.TabName)), _
index.ToString())
mItem.Selected = IIf(ActiveIndex.Equals(index), True, False)
mItem.Selectable = IIf(tabView.Selectable, True, False)
mItem.ToolTip = IIf(String.IsNullOrEmpty(tabView.TabToolTip), _
"", tabView.TabToolTip)
InnerMenu.Items.Add(mItem)
index += 1
Next
InnerMultiView = New MultiView()
InnerMultiView.ID = "tigerMltView"
For Each tabView As TabularView In Views
InnerMultiView.Views.Add(tabView)
Next
If InnerMultiView.Views.Count >= 0 AndAlso ActiveIndex < _
InnerMultiView.Views.Count Then
InnerMultiView.ActiveViewIndex = ActiveIndex
End If
AddHandler m_MultiView.ActiveViewChanged, AddressOf _
Mlt_ActiveIndexChanged
Me.Controls.Add(InnerMenu)
Me.Controls.Add(InnerMultiView)
End Sub
The Look and Feel
The Menu
control that’s embedded into this TabularMultiView
control can be assigned a cascading style sheet. I have supplied small images which can be used to round the edges of the MenuItem
s to mimic the look and feel of a tab. It is all up to you. Download my example solution attached to this article, run it, and play with the cascading style sheet properties. The image accompanying this article (top) shows my cascading style sheet in action.
Wrap-Up
The code that I have supplied along with this article contains many more features and functionality. These features exist only to provide additional functionality; however, they do not impact the TabStrip
’s basic functionality. I have also included the control's designer. I could not find a way to use the existing MultiView designer; however, I am working on implementing a more robust designer. The existing designer spits out the control name and description within the Visual Studio custom control box. The reason you cannot use the existing MultiView
designer is because the MultiView
’s designer only accepts inner property controls of type System.Web.UI.WebControls.View
. You can use the accompanied code to see how all of this is put together. Happy coding…!
Updates
- 7/15/06 - Original release.
- 7/21/06 - Code update.
- Added a helper method used to update the
ActiveIndex
(active tab).
- Added a download for just the code (class files).
- 9/14/06 - Code update.
- Fixed ViewState bug.
- Didn't retain viewstate in certain scenarios on postback.
- 9/28/06 - Code update (bugs fixed).
- Retaining Viewstate and setting the Active Index
- Instead of setting the active tab within the markup, you now set the active tab on page load using '
SetActiveView
' (zero-based index). With no active index set, an error is thrown. Therefore, on page load and when not in a postback, simply set the active view index to the desired tab. This will fix viewstate issues.
- Tab properties
- Allows you to change tab properties such as '
Selectable
', 'Name
', et. al. on postback.
- Project type
- Changed project download demo from a Web application project to a Web site project to alleviate some users' issues.